<template>
	<el-table ref="myTable" border :class="['my-table', { 'flex-grow-1': fit }]" :data="tableData" :row-style="setRowStyle" v-bind="$attrs">
		<template v-for="col in displayColumns" :key="`${col.prop}-${col.type}`">
			<!-- col 没有children 属性 -->
			<el-table-column v-if="!col.children" resizable v-bind="{ headerAlign: 'center', ...col }">
				<template #header="scope" v-if="!col.type">
					<slot :name="`${col.prop || 'default'}_header`" v-bind="{ ...scope, prop: col.prop }">
						<template v-if="!col.filter">
							{{ col.label }}
						</template>
						<template v-else>
							<el-popover trigger="click" :width="300">
								<template #reference>
									<span class="items-center display-inline-flex">
										{{ col.label }}
										<my-icon
											icon="Filter"
											class="pointer m-l-5"
											:color="queryParams[col.filter.prop] ? 'var(--el-color-primary)' : 'var(--el-table-header-text-color)'"
										/>
									</span>
								</template>
								<my-form-item
									class="m-b--18 m-l--12"
									:model="queryParams"
									:formItem="[
										Object.assign(
											{
												onChange: (val) => {
													searchFn();
												}
											},
											col.filter
										)
									]"
								/>
							</el-popover>
						</template>
					</slot>
				</template>

				<!-- col 没有type，formatter属性时，使用 slot 插槽-->
				<template #default="scope" v-if="!col.type && !col.formatter">
					<slot :name="col.prop || 'default'" v-bind="{ ...scope, prop: col.prop }">
						{{ valueFn(scope.row, col.prop) }}
					</slot>
				</template>
			</el-table-column>
			<!-- col 有children 属性， 直接返回slot插槽 -->
			<slot v-else :name="col.prop" :column="col"></slot>
		</template>
		<el-table-column
			v-if="columnFilter"
			prop="ColumnFilter"
			:resizable="false"
			width="26px"
			align="center"
			class-name="my-table--not-drag"
			fixed="right"
		>
			<template #header>
				<column-filter
					:columns="columnsProxy"
					v-model="displayColumnProps"
					@column-change-confirm="colChangeConfirm"
					@filterResetClick="filterResetClick"
				/>
			</template>
		</el-table-column>
		<!--暴露 el-table append 插槽-->
		<template #append v-if="$slots.append">
			<slot name="append"></slot>
		</template>
		<!-- 自动无数据提示 插槽-->
		<template #empty>
			<slot name="empty">
				<el-empty description="暂无数据" />
			</slot>
		</template>
	</el-table>
</template>

<script setup name="MyTable">
import ColumnFilter from './ColumnFilter.vue';
/**
 * MyTable 表格组件 报错问题为无法获取使用$props
 * @module components/my-table
 */
import Sortable from 'sortablejs';
import { debounce } from '@/utils/util';
import { insertAfter } from '@/utils/dom';

const emits = defineEmits(['column-change-confirm', 'on-column-sort', 'on-row-sort', 'on-reach-bottom', 'on-reach-top', 'on-scroll']),
	$attrs = useAttrs(),
	/**
	 * 开启表格列自定义
	 * <my-table :data="state.list" :columns="state.columns" columnFilter @selection-change="(val) => (tableSelection = val)"></my-table>
	 * state.columns=[{label: '配置名称',prop: 'configName'}]
	 *
	 * 开启表格列排序
	 * <my-table :data="state.list" :columns="state.columns" @selection-change="(val) => (tableSelection = val)"></my-table>
	 * state.columns=[{label: '配置名称',prop: 'configName',sortable: true}]
	 *
	 * 开启表格头部搜索
	 * <my-form :searchFn="() => {filterKeysFn();	refTable.reload();}"/>
	 * <my-table :data="state.list" :columns="state.columns" @selection-change="(val) => (tableSelection = val)" :queryParams="queryParams" :searchFn="() => refTable.reload()"></my-table>
	 * state.columns=[{label: '配置名称',prop: 'configName',filter:{prop: 'likeConfigName'}}]
	 *
	 * 属性参数，完全继承 el-table参数，并有以下扩展参数
	 * @property {Boolean} [fit = true] 是否占满父类
	 * @property {Array} [columns = []]  表格列定义，对象属性参数完全继承 el-table-column
	 * @property {String} [columnsKey = '_columns']  保存用户自定义表格列的唯一标识
	 * @property {Array} [data = []]  表格数据
	 * @property {Object | Boolean} [columnSortable = false] 是否启用列拖拽排序, 可以配置Sortable个性化参数
	 * @property {Object | Boolean} [rowSortable = false] 是否启用行拖拽排序, 可以配置Sortable个性化参数
	 * @property {Number} [distanceToButton = 50] 滚动到距离底部多少距离触发 on-reach-bottom 事件， table需要设置高度才有效
	 * @property {Boolean} [columnFilter] table列的自定义功能，默认为false
	 */
	props = defineProps({
		fit: {
			type: Boolean,
			default: true
		},
		data: {
			type: Array,
			default: () => []
		},
		columns: {
			type: Array,
			default: () => []
		},
		columnsKey: {
			type: String,
			default: 'columns'
		},
		columnSortable: {
			type: [Object, Boolean],
			default: false
		},
		rowSortable: {
			type: [Object, Boolean],
			default: false
		},
		distanceToButton: {
			type: Number,
			default: 50
		},
		columnFilter: {
			type: Boolean,
			default: false
		},
		/****表头查询功能****/
		queryParams: {
			type: Object,
			default: () => {}
		},
		searchFn: {
			type: Function
		},
		/***表格底部合计****/
		sum: {
			type: Boolean,
			default: false
		}
	}),
	$route = useRoute();

// 表格列定义数组
const myTable = shallowRef(null),
	columnsKey = `${$route.path}_${props.columnsKey}`,
	displayColumnProps = ref([]),
	// 表格行数据
	tableData = ref({});

// 原始表格数据
let columnsProxy = ref([]),
	// 列拖拽Sortable实例
	columnSortableInstance = null,
	// 行拖拽Sortable实例
	rowSortableInstance = null,
	// 上次滚动的位置
	lastScrollTop = 0;
/**
 * 监听表格数据
 */
watch(
	() => props.data,
	(val) => {
		tableData.value = val;
	},
	{ immediate: true }
);

/**
 * 实际表格渲染的列数组
 */
const displayColumns = computed(() =>
	columnsProxy.value.filter((col) => {
		// 有type的字段 或 没设置属性名称的列固定显示
		if (col.type || !col.prop) return true;
		return displayColumnProps.value.includes(col.prop);
	})
);
/**
 * 计算表头显示的列prop数组值
 */
function resetDisplayColumns() {
	displayColumnProps.value = columnsProxy.value
		.filter((col) => {
			if (!col.prop || col.type) return false;
			return col.display !== false;
		})
		.map((col) => col.prop);
}
/**
 * 监听表头显示列的数据，从而改变原始props.columns的display值
 */
watch(
	() => displayColumnProps.value,
	(val) => {
		columnsProxy.value.forEach((item) => {
			if (!(item.type || !item.prop)) {
				// 默认为显示修改
				if (val.includes(item.prop)) {
					if (item.display === false) delete item.display;
				} else {
					item.display = false;
				}
			}
		});
		$store.user.SetColumns(columnsKey, columnsProxy.value);
	}
);

/**
 * 监听表格column列数据
 */
watch(
	() => props.columns,
	(val) => {
		if (props.columnFilter && $store.user.GetColumns(columnsKey)) {
			columnsProxy.value = $store.user.GetColumns(columnsKey);
		} else {
			columnsProxy.value = [...val];
		}
		resetDisplayColumns();
	},
	{ immediate: true, deep: true }
);
/**
 * 自定义表头重置逻辑
 */
function filterResetClick() {
	props.columns.forEach((item, i) => {
		const { width, display } = columnsProxy[i] ?? {};
		if (width) {
			item.width = width;
		} else {
			delete item.width;
		}
		if (display) {
			item.display = display;
		} else {
			delete item.display;
		}
	});
}
/**
 * 表格的多层字段值显示函数
 * @param {*} row
 * @param {*} key
 */
function valueFn(row, key) {
	const keyArray = key?.split('.') || [];
	let data = '';
	keyArray.forEach((item, index) => {
		if (index === 0) {
			data = row[item];
		} else {
			data = data?.[item];
		}
	});
	return data;
}

/**
 * 列表筛选点击确定时触发
 * @event column-change-confirm
 * @param {Array[]} columnPropNames
 */
function colChangeConfirm() {
	emits('column-change-confirm', displayColumnProps.value);
}
/**
 * 内部调用排序方法，还原由Sortable拖拽改变的DOM顺序，然后修改数据，再由数据驱动改变DOM
 * @function
 * @private
 * @param {String} type 排序类型，row 或 column
 * @param {HTMLElement} container 容器元素DOM
 * @param {Array} data 数据数组
 * @param {Object} e Sortable事件对象
 */
function sort(type, container, data, e) {
	const tempData = type === 'column' ? [...data.filter((item) => item.display !== false)] : [...data],
		newIndex = e.newIndex,
		oldIndex = e.oldIndex,
		newTemp = tempData[newIndex],
		oldTemp = tempData[oldIndex];

	container.removeChild(e.item);
	const children = container.children;
	if (children.length) {
		oldIndex > 0 ? insertAfter(e.item, children[oldIndex - 1]) : container?.insertBefore(e.item, children[0]);
	} else {
		container.appendChild(e.item);
	}
	// 固定类型的列不运行拖拽和放置
	if (type === 'column' && data[newIndex] && data[newIndex].type && data[newIndex].fixed) return;

	[tempData[newIndex], tempData[oldIndex]] = [tempData[oldIndex], tempData[newIndex]];

	// 解决nextTick执行顺序问题,调整拖拽顺序后，change事件返回的数据未更新
	return new Promise((resolve) => {
		// 修复ele中重新设置表格配置时要重新描绘表格
		if (type === 'column') {
			const copyData = [...data],
				newIndex = copyData.findIndex((item) => item.label === newTemp.label && item.prop === newTemp.prop),
				oldIndex = copyData.findIndex((item) => item.label === oldTemp.label && item.prop === oldTemp.prop);

			[copyData[newIndex], copyData[oldIndex]] = [copyData[oldIndex], copyData[newIndex]];

			columnsProxy.value = [];
			nextTick(() => {
				columnsProxy.value = copyData;
				resolve(columnsProxy.value);
			});
		} else {
			tableData.value = [];
			nextTick(() => {
				tableData.value = tempData;
				resolve(tableData.value);
			});
		}
	});
}
/**
 * 实例化列Sortable
 */
function initColumnSortable() {
	const tr = myTable.value.$refs.tableHeader.getElementsByTagName('tr')[0];
	if (!tr) return;
	const options = Object.assign(
		{
			forceFallback: false,
			animation: 150
		},
		props.columnSortable,
		{
			handle: '.cell',
			filter: '.my-table--not-drag',
			onSort: (e) => {
				sort('column', tr, columnsProxy.value, e).then(() => {
					/**
					 * 列拖拽排序完成时触发
					 * @event on-column-sort
					 * @param {object} e Sortable事件对象
					 * @param {Array} columns 列定义数组
					 */
					emits('on-column-sort', e, columnsProxy.value);
				});
			}
		}
	);
	columnSortableInstance = new Sortable(tr, options);
}
/**
 * 实例化行Sortable
 */
function initRowSortable() {
	const tbody = myTable.value.$refs.bodyWrapper.getElementsByTagName('tbody')[0];
	if (!tbody) return;
	const options = Object.assign(
		{
			forceFallback: false,
			animation: 150
		},
		props.rowSortable,
		{
			onSort: (e) => {
				sort('row', tbody, tableData.value, e).then(() => {
					/**
					 * 行拖拽排序完成时触发
					 * @event on-row-sort
					 * @param {object} e Sortable事件对象
					 * @param {Array} data 行数据
					 */
					emits('on-row-sort', e, tableData.value);
				});
			}
		}
	);
	rowSortableInstance = new Sortable(tbody, options);
}
/**
 * 表格滚动加载数据逻辑
 */
function handleScroll(e) {
	const target = e.target;
	const dir = target.scrollTop - lastScrollTop > 0 ? 'down' : 'up';
	lastScrollTop = target.scrollTop;
	const scrollDistance = target.scrollHeight - target.scrollTop - target.clientHeight;
	if (dir === 'down' && scrollDistance <= props.distanceToButton) {
		/**
		 * 表格滚动到底部时触发
		 * @event on-reach-bottom
		 * @param {Number} scrollDistance 离底部的距离
		 */
		emits('on-reach-bottom', scrollDistance);
	}
	if (dir === 'up' && target.scrollTop <= props.distanceToButton) {
		/**
		 * 表格滚动到顶部时触发
		 * @event on-reach-top
		 * @param {Number} scrollTop 离顶部的距离
		 */
		emits('on-reach-top', target.scrollTop);
	}
	/**
	 * 表格滚动时触发
	 * @event on-scroll
	 * @param {Object} e 事件对象
	 */
	emits('on-scroll', e);
}

let proxyHandleScroll, bodyWrapper;
onMounted(() => {
	nextTick(() => {
		props.columnSortable && initColumnSortable();
		props.rowSortable && initRowSortable();
		proxyHandleScroll = debounce(handleScroll, 20, false);
		bodyWrapper = unref(myTable).$refs.bodyWrapper.querySelector('.el-scrollbar .el-scrollbar__wrap');
		bodyWrapper.addEventListener('scroll', proxyHandleScroll);
	});
});
onBeforeUnmount(() => {
	columnSortableInstance?.destroy?.();
	rowSortableInstance?.destroy?.();
	if (bodyWrapper && proxyHandleScroll) bodyWrapper.removeEventListener('scroll', proxyHandleScroll);
});

/**
 * 表格底部合计
 */
function setRowStyle({ row }) {
	if (props.sum === false) return {};
	return (row.amount === 0 ? true : Boolean(row.amount))
		? {
				background: '#f5f9fd',
				fontWeight: 'bold',
				position: 'sticky',
				zIndex: 2,
				bottom: row.amount == 0 ? '41px' : 0
			}
		: {};
}
</script>

<style lang="scss">
.my-table {
	.sortable-ghost {
		opacity: 0.3;
	}

	.sortable-ghost.my-table--not-drag {
		background: transparent;
	}

	.my-table--not-drag {
		.cell {
			padding: 0;
			cursor: pointer;
		}
	}

	&-filter {
		&__label {
			font-style: normal;
		}
	}
}
</style>
